package consensus

import (
	"encoding/hex"
	"fmt"
	"sync"
	"time"

	"github.com/tetratelabs/wazero/api"
	"github.com/tokentransfer/node/service"
	"github.com/tokentransfer/node/util"

	libaccount "github.com/tokentransfer/interfaces/account"
)

type Client struct {
	pk   libaccount.PublicKey
	time int64
}

type Service struct {
	nameOrAddress string
	wm            *service.WasmModule
	at            *service.Applet
	cm            map[string]*Client

	locker       *sync.RWMutex
	createTime   int64
	lastUsedTime int64
}

func CreateService(name string, wm *service.WasmModule, at *service.Applet) *Service {
	nowTime := time.Now().Unix()
	return &Service{
		nameOrAddress: name,
		wm:            wm,
		at:            at,
		cm:            make(map[string]*Client),

		locker:       &sync.RWMutex{},
		createTime:   nowTime,
		lastUsedTime: nowTime,
	}
}

func (s *Service) Start(maxGas int64, input string) (int64, bool, error) {
	s.locker.Lock()
	defer s.locker.Unlock()

	inputData, err := hex.DecodeString(input)
	if err != nil {
		return 0, false, err
	}
	inputItem := &service.Item{}
	err = inputItem.UnmarshalBinary(inputData)
	if err != nil {
		return 0, false, err
	}
	ok, err := cs.Verify(inputItem)
	if err != nil {
		return 0, false, err
	}
	if !ok {
		return 0, false, util.ErrorOfInvalid("verify", "message")
	}
	_, pk, err := as.NewPublicFromBytes(inputItem.GetPublicKey())
	if err != nil {
		return 0, false, err
	}
	a, err := pk.GenerateAddress()
	if err != nil {
		return 0, false, err
	}
	_, ok = s.cm[a.String()]
	if ok {
		return 0, false, util.ErrorOfInvalid("already in", "service")
	}
	nowTime := time.Now().Unix()
	s.lastUsedTime = nowTime

	timestamp := util.AsInt64(string(inputItem.Data))
	fmt.Println("timestamp", string(inputItem.Data), timestamp)
	if (nowTime-timestamp) < 0 || (nowTime-timestamp) > int64(10) {
		return 0, false, util.ErrorOfInvalid("time", "service")
	}

	cost := api.NewCostWith(maxGas)
	err = s.wm.Start(cost)
	if err != nil {
		return 0, false, err
	}
	s.cm[a.String()] = &Client{pk: pk, time: time.Now().UnixMilli()}
	return cost.GetCost(), true, nil
}

func (s *Service) Join(maxGas int64, input string) (int64, bool, error) {
	s.locker.Lock()
	defer s.locker.Unlock()

	inputData, err := hex.DecodeString(input)
	if err != nil {
		return 0, false, err
	}
	inputItem := &service.Item{}
	err = inputItem.UnmarshalBinary(inputData)
	if err != nil {
		return 0, false, err
	}
	ok, err := cs.Verify(inputItem)
	if err != nil {
		return 0, false, err
	}
	if !ok {
		return 0, false, util.ErrorOfInvalid("verify", "message")
	}
	_, pk, err := as.NewPublicFromBytes(inputItem.GetPublicKey())
	if err != nil {
		return 0, false, err
	}
	a, err := pk.GenerateAddress()
	if err != nil {
		return 0, false, err
	}
	// _, ok = s.cm[a.String()]
	// if ok {
	// 	return 0, false, util.ErrorOfInvalid("already in", "service")
	// }

	inputContent := string(inputItem.Data)
	inputList := util.Split(inputContent, ",")
	if len(inputList) != 2 {
		return 0, false, util.ErrorOfInvalid("input", "service")
	}
	joinHash := inputList[0]
	joinTime := inputList[1]

	nowTime := time.Now().Unix()
	s.lastUsedTime = nowTime
	timestamp := util.AsInt64(joinTime)
	if (nowTime-timestamp) < 0 || (nowTime-timestamp) > int64(10*time.Second) {
		return 0, false, util.ErrorOfInvalid("time", "service")
	}

	cost := api.NewCostWith(maxGas)
	if joinHash != s.at.GetHash().String() {
		return 0, false, util.ErrorOfUnmatched("name", "service", s.at.GetHash().String(), joinHash)
	}
	s.cm[a.String()] = &Client{pk: pk, time: time.Now().UnixMilli()}

	return cost.GetCost(), true, nil
}

func (s *Service) Leave(maxGas int64, input string) (int64, bool, error) {
	s.locker.Lock()
	defer s.locker.Unlock()

	inputData, err := hex.DecodeString(input)
	if err != nil {
		return 0, false, err
	}
	inputItem := &service.Item{}
	err = inputItem.UnmarshalBinary(inputData)
	if err != nil {
		return 0, false, err
	}
	ok, err := cs.Verify(inputItem)
	if err != nil {
		return 0, false, err
	}
	if !ok {
		return 0, false, util.ErrorOfInvalid("verify", "message")
	}
	_, pk, err := as.NewPublicFromBytes(inputItem.GetPublicKey())
	if err != nil {
		return 0, false, err
	}
	a, err := pk.GenerateAddress()
	if err != nil {
		return 0, false, err
	}
	_, ok = s.cm[a.String()]
	if !ok {
		return 0, false, util.ErrorOfInvalid("not in", "service")
	}

	inputContent := string(inputItem.Data)
	inputList := util.Split(inputContent, ",")
	if len(inputList) != 2 {
		return 0, false, util.ErrorOfInvalid("input", "service")
	}
	joinHash := inputList[0]
	joinTime := inputList[1]

	nowTime := time.Now().Unix()
	s.lastUsedTime = nowTime
	timestamp := util.AsInt64(joinTime)
	if (nowTime-timestamp) < 0 || (nowTime-timestamp) > int64(10*time.Second) {
		return 0, false, util.ErrorOfInvalid("time", "service")
	}

	cost := api.NewCostWith(maxGas)
	if joinHash != s.at.GetHash().String() {
		return 0, false, util.ErrorOfUnmatched("name", "service", s.at.GetHash().String(), joinHash)
	}
	delete(s.cm, a.String())

	return cost.GetCost(), true, nil
}

func (s *Service) Process(maxGas int64, input string) (int64, string, error) {
	inputData, err := hex.DecodeString(input)
	if err != nil {
		return 0, "", err
	}
	inputItem := &service.Item{}
	err = inputItem.UnmarshalBinary(inputData)
	if err != nil {
		return 0, "", err
	}
	ok, err := cs.Verify(inputItem)
	if err != nil {
		return 0, "", err
	}
	if !ok {
		return 0, "", util.ErrorOfInvalid("verify", "message")
	}
	s.lastUsedTime = time.Now().Unix()
	cost := api.NewCostWith(maxGas)
	itemInput := hex.EncodeToString(inputItem.Data)
	itemOutput, err := s.wm.Process(cost, itemInput)
	if err != nil {
		return cost.GetCost(), "", err
	}
	itemOutputData, err := hex.DecodeString(itemOutput)
	if err != nil {
		return cost.GetCost(), "", err
	}
	outputItem, err := s.at.AddMessage(inputItem, itemOutputData)
	if err != nil {
		return cost.GetCost(), "", err
	}
	outputData, err := outputItem.MarshalBinary()
	if err != nil {
		return cost.GetCost(), "", err
	}
	return cost.GetCost(), hex.EncodeToString(outputData), nil
}

func (s *Service) Stop(maxGas int64) (int64, error) {
	cost := api.NewCostWith(maxGas)
	err := s.wm.Stop(cost)
	if err != nil {
		return cost.GetCost(), err
	}
	err = s.wm.Close(cost)
	return cost.GetCost(), err
}

type Services []*Service

func (ss Services) Len() int {
	return len(ss)
}
func (ss Services) Less(i, j int) bool {
	return ss[i].createTime < ss[j].createTime
}
func (ss Services) Swap(i, j int) {
	temp := ss[i]
	ss[i] = ss[j]
	ss[j] = temp
}
func (ss Services) List() []string {
	ll := len(ss)
	list := make([]string, ll)
	for i := 0; i < ll; i++ {
		list[i] = ss[i].at.GetHash().String()
	}
	return list
}
